Capstone

Autor

IT; JMR

Data de Publicação

17 de novembro de 2025

1 Introdução

O presente projeto foi desenvolvido no âmbito do Capstone do curso Fundamentos de Data Science na Saúde: Aplicações com R da Escola Nacional de Saúde Pública (Escola Nacional de Saúde Pública (ENSP NOVA) 2025).

Fonte: (RStudio, Inc. 2025)

O trabalho tem como propósito aplicar técnicas de ciência de dados na análise de uma base real de morbilidade hospitalar, a base de Grupos de Diagnóstico Homogéneo (GDH) disponibilizada pela Administração Central do Sistema de Saúde (ACSS). A crescente complexidade dos cuidados hospitalares e a transição para o modelo das Unidades Locais de Saúde (ULS) colocam desafios importantes para a gestão integrada dos recursos e para a equidade no acesso. Neste contexto, comparar realidades distintas (como as ULS do Médio Ave e de Amadora-Sintra) permite compreender diferenças territoriais na carga de doença e na resposta dos serviços hospitalares, contribuindo para uma visão mais informada sobre o desempenho do SNS. Assim, este estudo combina análise demográfica, epidemiológica e operacional, explorando a base de dados e relacionando-a com variáveis geográficas e populacionais.

1.1 A ULS

  • Caracterizar a população residente nas áreas correspondentes às duas ULS (população 2017)

    • Distribuição idade e sexo

  • Concelhos

    • Amadora

      • Total residentes (2017): 172.324
    • Sintra

      • Total residentes (2017): 380.125
  • Fonte dados: INE

  • Concelhos

    • Vila Nova de Famalicão

      • Total residentes (2017): 132.204
    • Santo Tirso

      • Total residentes (2017): 68.512
    • Trofa

      • Total residentes (2017): 38.427
  • Fonte dados: INE


1.1.1 Bases de dados utilizadas

  • base_gdh_2017_icd10_no_freg

  • codigos_diagnostico_icd10

  • geo_linkage_2024_v6

  • grupos_gdh

1.2 Objetivos do estudo

1.3 Objetivo geral

O projecto centra-se na análise comparativa da situação nas ULS do Médio Ave e Amadora-Sintra.

Analisar e comparar os padrões de morbilidade hospitalar e a resposta assistencial entre as ULS do Médio Ave e Amadora-Sintra, utilizando dados da base de GDH do SNS, com foco na caracterização demográfica, perfil de diagnósticos e desempenho hospitalar.

  1. ⁠ ⁠Caracterizar a população residente nas áreas correspondentes às duas ULS, segundo idade, sexo e perfil de morbilidade.
  2. Identificar e comparar os principais GDH e condições de saúde mais prevalentes em cada região.
  3. ⁠Avaliar diferenças entre ULS
    1. limitações de codificação e de qualidade dos dados.

1.4 Objetivos específicos

  • Caracterizar a população residente nos concelhos correspondentes às áreas das ULS Amadora/Sintra

    • distribuição idade e sexo

    • concelho

  • Caracterizar os problemas de saúde e identificar os mais frequentes (base)

    • comparar totais
    • duração de internamento
    • perfil de morbilidade (mortalidade)
    • problemas de saúde (top 5)
    • taxa de mortalidade

2 Importação e preparação dos dados

Código
set.seed(123)

rm(list = ls(all.names = TRUE)) 
required_packages <- c(
                       "tidyverse",
                       "rio",
                       "scales",
                       "here",
                       "patchwork",
                       "sf", 
                       "ggthemes",
                       "giscoR",
                       "eurostat",
                       "sysfonts", 
                       "showtext", 
                       "scales",
                       "geodata", 
                       "osmdata", 
                       "leaflet",
                       "janitor",
                       "assertr",
                       "data.validator",
                       "forcats",
                       "data.table",
                       "broom",
                       "gt",
                       "gtsummary",
                       "glm2",
                       "performance",
                       "see",
                       "readxl",
                       "purrr",
                       "tibble",
                       "ggridges",
                       "tidymodels",
                       "tidyclust",
                       "factoextra",
                       "rstatix"
                       )    

for (pkg in required_packages) {
 
  if (!pkg %in% rownames(installed.packages())) {
    install.packages(pkg)
  }

  library(pkg, character.only = TRUE)
}
remove(required_packages)
remove(pkg)

2.1 Importação dos dados

Código
gdh_base_raw <-  import("data/data_gdh/base_gdh_icd10_no_freg.csv")

code_residence <- import("data/data_gdh/codigos_residencia.csv") 


geo_linkage <- import("data/data_gdh/geo_linkage_2024_v6.csv")

comm_pt <- st_read("data/map_json_portugal/concelhos_portugal_light.json")
Reading layer `concelhos_portugal' from data source 
  `/Users/joaomiguelrocha/documents_local/GitHub/capstone_r_fundamentals/data/map_json_portugal/concelhos_portugal_light.json' 
  using driver `TopoJSON'
Simple feature collection with 278 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: -9.50088 ymin: 37.01021 xmax: -6.189142 ymax: 42.1482
CRS:           NA
Código
icd10_codes <- import("data/data_gdh/codigos_diagnostico_icd10.csv")

gdh_group <- import("data/data_gdh/grupos_gdh.csv")

3 Análise Exploratória e Limpeza

Código
df_summarise <- function(df) {
    tibble(
      variable = names(df),
      type = map_chr(df, ~ class(.x)[1]),
      n_total = nrow(df),
      n_distinct = map_int(df, ~ if (is.character(.x) || is.factor(.x))
                              dplyr::n_distinct(.x, na.rm = TRUE) # especifica package para evitar conflito
                            else NA_integer_),
    n_NA = map_int(df, ~ {
      if (is.character(.x)) {
        sum(is.na(.x) | .x == "", na.rm = TRUE)  # contar NA e células em branco 
      } else {
        sum(is.na(.x))
      }
    })
  )
}
Código
# Funções 
var_summarise <- summarise_variable <- function(x) {
  if (is.numeric(x)) {
    out <- list(
      Min = min(x, na.rm = TRUE),
      Max = max(x, na.rm = TRUE),
      Mean = mean(x, na.rm = TRUE),
      Median = median(x, na.rm = TRUE),
      Quantiles = quantile(x, na.rm = TRUE),
      Total = sum(x, na.rm = TRUE),
      NAs = sum(is.na(x))
    )
  } else if (is.character(x) || is.factor(x)) {
    tbl <- table(x, useNA = "ifany")
    freq <- as.numeric(tbl)
    prop <- as.numeric(prop.table(tbl))
    cum_freq <- cumsum(freq)
    names(freq) <- names(prop) <- names(tbl)
    out <- list(
      Unique_Values = unique(na.omit(x)),
      Frequency = freq,
      Proportion = prop,
      CumFreq = cum_freq,
      NAs = sum(is.na(x))
    )
  } else {
    out <- list(Warning = "Variable type not supported")
  }
  return(out)
}

var_summarise_df <- function(df, x) {
  # Extract the variable vector from df using x as column name (string)
  var <- df[[x]]
  
  if (is.numeric(var)) {
    df_out <- data.frame(
      Statistic = c("Min", "Max", "Mean", "Median", "Total", "NAs"),
      Value = c(
        min(var, na.rm = TRUE),
        max(var, na.rm = TRUE),
        mean(var, na.rm = TRUE),
        median(var, na.rm = TRUE),
        sum(var, na.rm = TRUE),
        sum(is.na(var))
      )
    )
    # Add quantiles as separate rows
    quants <- quantile(var, na.rm = TRUE)
    quant_df <- data.frame(
      Statistic = paste0("Quantile_", names(quants)),
      Value = as.numeric(quants)
    )
    df_out <- rbind(df_out, quant_df)
  } else if (is.character(var) || is.factor(var)) {
    tbl <- table(var, useNA = "ifany")
    freq <- as.numeric(tbl)
    prop <- prop.table(tbl)
    cum_freq <- cumsum(freq)
    df_out <- data.frame(
      Value = names(tbl),
      Frequency = freq,
      Proportion = prop,
      CumFrequency = cum_freq
    )
  } else {
    df_out <- data.frame(Warning = "Variable type not supported")
  }
  
  return(df_out)
}

3.1 Base Gdh e variáveis

Código
gdh_raw_summary <- df_summarise(gdh_base_raw)

tabela0 <- gdh_raw_summary |> 
  gt()

tabela0
variable type n_total n_distinct n_NA
seq_number character 1711879 1711869 0
hosp_id character 1711879 56 0
sexo character 1711879 3 0
idade integer 1711879 NA 0
distrito integer 1711879 NA 0
concelho integer 1711879 NA 0
hora_entrada integer 1711879 NA 0
dias_int integer 1711879 NA 0
dsp character 1711879 9 13
adm_tip character 1711879 2 0
hora_urgência integer 1711879 NA 1327998
gcd_APR31 integer 1711879 NA 0
tipo_port APR31 character 1711879 2 0
severidade_APR31 character 1711879 5 225
mortalidade_APR31 character 1711879 5 225
freguesia integer 1711879 NA 0
cod_diagnostico character 1711879 18955 0
Código
gtsave(tabela0, filename = "gdh_tabela_limpeza.html") 

Avaliação inicial

Total observações vs NA

  • n = 1711879

  • NAs

    • hora_urgencia: n(1.327.998) -> apagar variável

    • severidade: n(225) em branco -> transformar em “desconhecido”

    • mortalidade n(225) em branco -> “NA” - transformar em desconhecido para proporções

Erros tipo variaveis

  • gcd_apr31 - int devia ser chr

  • concelho e distrito - estão como int - sao codigos numéricos deve ser chr

  • freguesia - remover, sem valores que possamos utilizar

Código
# mapeia valores presentes na base de dados de categorias de variávies chr 

gdh_unique <- gdh_base_raw |>
  select(where(is.character)) |> 
  map(unique)

Outros:

  • dias internamento - como interpretar? remover 0?

  • o que seria expectavel (dicionario) vs obs variaveis

    • adm_tip - apenas dois tipos, mas 0 missings

    • dsp - como interpretar? tratar falecidos

seq_number - sem NA 
hosp_id - 56 possiveis - algum missing? 
sexo - 3 variaveis como esperado, 
idade - todas têm entrada - 0 são mesmo idade? se tiver meses? 
distrito - transformar em chr e contar diferentes (30 valores possiveis)
concelho - igual a distrito 
freguesia - inútil - remover 
hora_entrada - valores em s, a partir das 0h do dia da entrada na instituição - verificar 0 e como interpretar  
dias_int - Total de dias de estadia do utente na instituição de saúde, em conformidade com a definição estatística de tempo de internamento, constante na portaria em vigor à data de extração dos dados.- qual a definicao estatistica? 
dsp - 9 valores possiveis, 9 entradas , contar "Desconhecido" como NA 
adm_tip - 8 valores possiveis, apenas 2 , 0 NAs
hora_urgência - Hora de entrada no serviço de urgência da instituição de saúde. Os valores são apresentados em segundos, contados a partir das zero horas do dia em que o utente deu entrada, no serviço de urgência da instituição - 
gcd_APR31 - mudar para chr, 27 valores possiveis, 99 - Erro
tipo_port APR31 - 2 possiveis, 0 NA - bem 
severidade_APR31 - mortalidade_APR31 - 4 valores possiveis existem 5 - 225 NA são todos missing
cod_diagnostico - ver se dao todos match com icd10.csv 

3.2 Limpeza

Código
gdh_base_clean <- gdh_base_raw |> 
  clean_names() |> 
  rename(
    cod_dist = distrito,
    cod_conc = concelho,
    icd10_code = cod_diagnostico
  ) |> 
  mutate(
    across(c(cod_dist, cod_conc), ~sprintf("%02d", .x), .names = "{.col}_chr"),
    dt_mun = paste0(cod_dist_chr, cod_conc_chr),
    code_length = nchar(icd10_code),
    idade_cat = cut(idade, c(0, 18, 65, Inf), right = FALSE)
    ) |> 
  filter( 
    idade != -1, 
    cod_conc_chr != "99",  
    cod_dist_chr != "99", 
    dt_mun != "9999", 
    sexo != "Indeterminado"
    ) |> 
  select(
    -hora_urgencia,
    -freguesia,
    -hosp_id,
    -hora_entrada)
Código
# Summary Dados Limpos 

gdh_clean_summary <- df_summarise(gdh_base_clean)

tabela0_1 <- gdh_clean_summary |> 
  gt()

tabela0_1
variable type n_total n_distinct n_NA
seq_number character 1560164 1560154 0
sexo character 1560164 2 0
idade integer 1560164 NA 0
cod_dist integer 1560164 NA 0
cod_conc integer 1560164 NA 0
dias_int integer 1560164 NA 0
dsp character 1560164 9 13
adm_tip character 1560164 2 0
gcd_apr31 integer 1560164 NA 0
tipo_port_apr31 character 1560164 2 0
severidade_apr31 character 1560164 5 208
mortalidade_apr31 character 1560164 5 208
icd10_code character 1560164 18354 0
cod_dist_chr character 1560164 29 0
cod_conc_chr character 1560164 24 0
dt_mun character 1560164 308 0
code_length integer 1560164 NA 0
idade_cat factor 1560164 3 0

3.2.1 ICD10

Código
icd10_summary <- df_summarise(icd10_codes)

tabela01 <- icd10_summary |> 
  gt() 

tabela01 
variable type n_total n_distinct n_NA
Código ICD-10-CM character 95093 94569 0
Short_Descp ICD-10-CM character 95093 94233 0
Long_Descp ICD-10-CM character 95093 94319 0
PT character 95093 92964 1180
Código
icd10_codes_pt <- icd10_codes |> 
  clean_names() |>
  rename(
    descricao_pt = pt,
    descricao_en = short_descp_icd_10_cm, 
    icd10_code = codigo_icd_10_cm
  ) |> 
  select(
    icd10_code,
    descricao_en,
    descricao_pt
  ) |> 
  mutate(
    code_length = nchar(icd10_code)
  ) |> 
  distinct(icd10_code, .keep_all = TRUE) # remove duplicados 

Obs.:

  • todas as variáveis são chr

  • PT: 1180 empty -> mas descrição em inglês está preenchida

  • remover descrição longa em Inglês

  • apagados duplicados

3.2.2 GDH

Código
gdh_group_summary <- df_summarise(gdh_group)

tabela02 <- gdh_group_summary |> 
  gt() 

tabela02
variable type n_total n_distinct n_NA
gcd_APR31 integer 27 NA 0
Designacao character 27 27 0
Código
gdh_group <- gdh_group |> 
  clean_names() |> 
  rename(
    descricao_apr31 = designacao
  )

Obs,;

  • gcd_apr31 - int em vez de chr - transformar

3.2.3 Code Residence

Código
code_residence_sum <- df_summarise(code_residence)

tabela03 <- code_residence_sum |> 
  gt() 

tabela03
variable type n_total n_distinct n_NA
COD_DIST integer 313 NA 0
COD_CONC integer 313 NA 0
COD_FREG integer 313 NA 0
DES_DCF character 313 311 0
Código
code_residence <- code_residence |>
  clean_names() |> 
  mutate(
    across(c(cod_dist, cod_conc), ~sprintf("%02d", .x), .names = "{.col}_chr"),
    dt_mun = paste0(cod_dist_chr, cod_conc_chr)
    )

# transformar int em chr

3.2.4 COMM-PT

Código
comm_pt_summary <- df_summarise(comm_pt)

tabela04 <- comm_pt_summary |> 
  gt() 

tabela04
variable type n_total n_distinct n_NA
id character 278 0 278
GID_1 character 278 18 0
NAME_1 character 278 18 0
GID_2 character 278 278 0
NAME_2 character 278 278 0
geometry sfc_MULTIPOLYGON 278 NA 0
Código
comm_pt <- comm_pt |>
  rename(municipio = NAME_2, distrito = NAME_1) |> 
  clean_names()

3.2.5 Geo linkage

Código
geo_linkage_summary <- df_summarise(geo_linkage)

tabela05 <- geo_linkage_summary |> 
  gt() 

tabela05
variable type n_total n_distinct n_NA
freguesia_2025 character 3260 2989 0
abreviatura_freguesia_2025 character 3260 2988 0
freguesia_2013 character 3260 2874 2
fr_2025 character 3260 111 0
dicofre_2025 character 3260 3259 0
dicofre_2013 character 3260 3093 2
dicofre_2013_2 character 3260 3093 2
municipio_2013 character 3260 308 0
dt_mun integer 3260 NA 0
mn_2025 integer 3260 NA 0
municipio_2024_cod character 3260 308 0
municipio_2013_cod character 3260 308 0
municipio_2002_cod character 3260 308 0
distrito_2013 character 3260 29 0
dt_2025 integer 3260 NA 0
distrito_2013_cod integer 3260 NA 0
nuts3_2013 character 3260 25 0
nuts3_2013_cod character 3260 25 0
nuts3_2002_cod integer 3260 NA 223
nuts3_2024 character 3260 26 0
nuts3_2024_cod character 3260 26 0
nuts2_2013 character 3260 7 0
nuts2_2013_cod integer 3260 NA 0
nuts2_2024 character 3260 9 0
nuts2_2024_cod character 3260 9 0
nuts1_2013 character 3260 3 0
nuts1_2013_cod integer 3260 NA 0
pais character 3260 1 0
pais_cod character 3260 1 0
uls_hierarquia_csp character 3260 40 210
uls_2024 character 3260 55 0
uls_2023 character 3260 55 0
aces_2022 character 3260 72 0
aces_2022_cod integer 3260 NA 0
ars_2022 character 3260 7 0
ars_2022_cod integer 3260 NA 0
regiao_2024 character 3260 7 0
Código
geolink_target <- c("dt_mun", "distrito_2013","municipio_2013_cod", "municipio_2013", "uls_2024", "regiao_2024", "nuts3_2013", "nuts3_2013_cod", "nuts3_2024", "nuts3_2024_cod", "municipio_2024_cod", "uls_2023", "uls_hierarquia_csp")


geo_linkage_clean <- geo_linkage |>
  clean_names() |>  
  mutate(
    dt_mun = str_pad(as.character(dt_mun), 4, pad = 0),
    mun_cod = str_pad(as.character(municipio_2013_cod), 2, pad = 0),
    dt_cod = str_pad(as.character(distrito_2013_cod), 2, pad = 0)
    ) |> 
  select(all_of(geolink_target)) |> 
  distinct(dt_mun, .keep_all = TRUE) |>
  rename(
    municipio = municipio_2013,
    distrito = distrito_2013
  )

3.3 Join bases

Código
# CÓDIGOS RESIDÊNCIA 
gdh_base_join <- gdh_base_clean |> 
  left_join(
    code_residence,
    by = "dt_mun"
  )

# join gdh group and icd10 codes
gdh_base_join <- gdh_base_clean |> 
  left_join(
    icd10_codes_pt, by = c("icd10_code", "code_length")
  ) |> 
  left_join(
    gdh_group, by = "gcd_apr31"
    )


gdh_join_final <- gdh_base_join |> 
  left_join(
    geo_linkage_clean,
    by = "dt_mun") 

# Base de dados apenas com ULS Amadora/Sintra, ULS Médio Ave
gdh_uls <- gdh_join_final |>
  filter(
    cod_dist != 99
  ) |> 
  filter(uls_2024 %in% c("ULS de Amadora/Sintra", "ULS do Médio Ave")) 

4 Visualização dos dados

4.0.1 Idade

Código
# variabilidade 

idade_sumario <- var_summarise_df(gdh_base_clean, "idade")

idade_sumario
       Statistic        Value
1            Min 0.000000e+00
2            Max 1.090000e+02
3           Mean 5.667767e+01
4         Median 6.300000e+01
5          Total 8.842646e+07
6            NAs 0.000000e+00
7    Quantile_0% 0.000000e+00
8   Quantile_25% 4.300000e+01
9   Quantile_50% 6.300000e+01
10  Quantile_75% 7.500000e+01
11 Quantile_100% 1.090000e+02
Código
# Base dados completa
p4_0 <- gdh_base_clean |>
  ggplot() +
  geom_boxplot(
    aes(y = idade)
    ) +
  # facet_grid(~sexo) +
  theme_minimal() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_0

Código
idade_sumario <- idade_sumario |> 
  mutate(
    value = round(Value, 0)
  ) |> 
  filter(
    Statistic != "Total"
  )

# add labels - ver como corrigir 
p4_01 <- gdh_base_clean |>
  ggplot() +
  geom_boxplot(
    aes(x = 1, y = idade)
    ) +
  geom_text(
    data = idade_sumario, 
    aes(x = 1, y = value, label = value),
    hjust = -0.3,
    vjust = -0.3
    ) + 
  theme_minimal() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_01

Código
# ULS MAve e ULS A/S
p4_02 <- gdh_base_clean |>
  ggplot() +
  geom_density(
    aes(x = idade)
    ) +
  facet_grid(~sexo) +
  theme_minimal() 

  
p4_02

4.0.2 Sexo

Código
sexo_sumario <- var_summarise_df(gdh_base_clean, "sexo")

# Base dados completa
p4_1 <- gdh_base_clean |>
  filter(
    sexo != "Indeterminado"
  ) |> 
  ggplot() +
  geom_boxplot(
    aes(y = idade, fill = sexo)
    ) +
  coord_cartesian() +
  theme_classic() +
  theme(
    axis.line.x = element_blank(),
    axis.text.x = element_blank()
  )
  
p4_1

Código
# Total Episódios por ULS e Tipo de episodio 
total_ep_uls <- gdh_uls |> 
  group_by(uls_2024, sexo, idade, idade_cat, adm_tip) %>%
  summarise(total = n()) %>%
  arrange(uls_2024)

p4_2 <- ggplot(
  gdh_uls, 
  aes(x= idade)
) +
  geom_density(alpha=.4) +
  facet_grid(~uls_2024) +
  labs(title = "Distribuição de Episodios por idade",
       y = "Densidade",
       x = "Idade", 
       caption = "Autores: IT e JMR") +
  theme_minimal()

p4_2

Código
p4_3 <- gdh_uls |> 
  group_by(idade_cat) |> 
  ggplot() +
  geom_bar(
    aes(x = uls_2024, fill = idade_cat),
    position = "fill"
  ) +
  geom_text(
    aes(
      x = uls_2024, 
      fill = idade_cat, 
      label = after_stat(count)
      ),
    stat = "count",
    position = position_fill(vjust = 0.5)
    ) +
    theme(
    legend.title = element_blank(),
    legend.position = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
    ) +
  coord_flip() +
  theme_classic() 


p4_3

Código
p4_31 <- gdh_uls |> 
  ggplot() +
  geom_bar(
    aes(x = uls_2024, fill = idade_cat),
    position = "stack"
  ) +
  geom_text(
    aes(x = uls_2024, fill = idade_cat, label = after_stat(count)),
    stat = "count",
    position = position_stack(vjust = 0.5)
  ) +
  theme_classic() 
  

p4_31

Código
# Distribuição por sexo ULS 
p4_4 <- total_ep_uls |> 
  ggplot(aes(x = uls_2024, fill = sexo)) +
  geom_bar(position = "fill") +
  scale_fill_viridis_d(end = .5) +
  theme(
    axis.line.x  = element_blank(),
    axis.line.y = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    )

p4_4

Código
total_uls_sex_age <- gdh_uls |> 
  group_by(uls_2024, adm_tip, sexo = as.factor(sexo), idade_cat) %>%
  summarise(total = n()) %>%
  arrange(sexo)


tabela4 <- tbl_summary(
  gdh_uls,
  include = c(uls_2024, adm_tip, sexo, idade_cat),
  by = sexo,
  missing = "ifany", 
) |>
  modify_spanning_header(c("stat_1", "stat_2") ~ "**Sexo**") %>%
  add_n() |>  
  add_p() |> 
  modify_header(label = "**Variável**") |>
  bold_labels() 

tabela4 <- tabela4 |> 
  as_gt() |>  
  tab_header(
    title = md("**Episódios por ULS**") ,
    subtitle = "Por sexo"
  ) |>
  tab_source_note(
    source_note = md("Fonte: gdh")
  ) |>
  fmt_number( decimals = 3) |>   
  opt_stylize(style = 1, color = "gray") |> 
  opt_align_table_header(align = "left") 

tabela4
Episódios por ULS
Por sexo
Variável N
Sexo
p-value2
Feminino
N = 64,2311
Masculino
N = 58,6171
uls_2024 122,848

0.000
    ULS de Amadora/Sintra
43,417 (68%) 38,384 (65%)
    ULS do Médio Ave
20,814 (32%) 20,233 (35%)
adm_tip 122,848

0.000
    Programada
40,687 (63%) 39,674 (68%)
    Urgente
23,544 (37%) 18,943 (32%)
idade_cat 122,848

0.000
    [0,18)
6,305 (9.8%) 7,615 (13%)
    [18,65)
34,209 (53%) 24,021 (41%)
    [65,Inf)
23,717 (37%) 26,981 (46%)
1 n (%)
2 Pearson’s Chi-squared test
Fonte: gdh
Código
# Contar total de observações por grupo etário
contagem_idade <- gdh_uls %>%
  group_by(uls_2024, as.factor(sexo), idade_cat) %>%
  summarise(total = n()) %>%
  arrange(idade_cat)

4.0.3 Proporções

Código
p6 <- gdh_uls |> 
  count(uls_2024, severidade_apr31) |> 
  mutate(freq = (n / sum(n))) |> 
  mutate(cum = cumsum(freq)) |> 
  ggplot(
    aes(x = uls_2024, y = freq, fill = severidade_apr31)
    )+
  geom_bar(position = "stack", stat = "identity") +
  scale_fill_viridis_d(end = .5) +
  geom_text(aes(label = percent(freq, accuracy = .1)), position = position_stack(vjust = .1), colour = "white") +
  labs(fill = "severidade_apr31", y = "Proportion", x = "ULS 2024")

p6

4.1 Perfil de morbilidade

Código
total_mort_apr31 <- gdh_uls |>
  mutate(
    mortalidade_apr31 = na_if(mortalidade_apr31, ""),         # transforma "" em NA
    mort_cat = fct_explicit_na(factor(mortalidade_apr31), na_level = "NA")
  ) |>
  group_by(uls_2024, mort_cat) |>
  summarise(total = n(), .groups = "drop")

p7 <- total_mort_apr31 |>  
  ggplot(
    aes(x = uls_2024, y = total, fill = mort_cat)
    )+
  geom_bar(position = "stack", stat = "identity") +
  scale_fill_viridis_d(end = 0.5) +
  geom_text(aes(label = total), position = position_stack(vjust = 1), colour = "white") +
  labs(y = "Total de episódios", x = "ULS", fill = "Severidade")

p7

Código
p7_1 <- gdh_uls |> 
  count(uls_2024, mortalidade_apr31) |> 
  mutate(freq = (n / sum(n))) |> 
  mutate(cum = cumsum(freq)) |> 
  ggplot(
    aes(x = uls_2024, y = freq, fill = mortalidade_apr31)
    )+
  geom_bar(position = "fill", stat = "identity") +
  scale_fill_viridis_d(end = 0.5) +
  labs(fill = "mortalidade_apr31", y = "Proporção", x = "ULS 2024")

p7_1

Código
gdh_ridges <- gdh_base_join |> 
  left_join(
    geo_linkage_clean,
    by = "dt_mun"
  )

anti_join(gdh_base_join, geo_linkage_clean, by = "dt_mun")
 [1] seq_number        sexo              idade             cod_dist         
 [5] cod_conc          dias_int          dsp               adm_tip          
 [9] gcd_apr31         tipo_port_apr31   severidade_apr31  mortalidade_apr31
[13] icd10_code        cod_dist_chr      cod_conc_chr      dt_mun           
[17] code_length       idade_cat         descricao_en      descricao_pt     
[21] descricao_apr31  
<0 rows> (or 0-length row.names)
Código
p8 <- gdh_ridges |>
  group_by(regiao_2024) |> 
  ggplot(aes(x = idade, y = regiao_2024)) + 
  geom_density_ridges() +
  theme_classic()

p8

Código
p8_1 <- gdh_uls |>
  group_by(uls_2024) |> 
  ggplot(aes(x = idade, y = uls_2024)) + 
  geom_density_ridges() +
  theme_classic()

p8_1

Código
p8_2 <- gdh_uls |>
  group_by(dsp) |> 
  ggplot(aes(x = idade, y = dsp)) + 
  geom_density_ridges() +
  theme_classic()

p8_2

Código
p8_3 <- gdh_uls |>
  group_by(sexo) |> 
  ggplot(aes(x = idade, y = sexo)) + 
  geom_density_ridges() +
  theme_classic()

p8_3

Código
dias_int_sum <- var_summarise_df(gdh_base_clean, "dias_int")

dias_int_sum <-  dias_int_sum |> 
  mutate(
    value = round(Value, 2)
  )

5 Modelos Estatísticos

Código
gdh_model <- gdh_uls |> 
  mutate(
    sexo = as.factor(sexo)
  ) |> 
  filter(
    dias_int > 0
  )



model1 <- lm(dias_int ~ idade, data = gdh_model)

summary(model1)

Call:
lm(formula = dias_int ~ idade, data = gdh_model)

Residuals:
   Min     1Q Median     3Q    Max 
-12.42  -5.68  -2.78   0.50 359.96 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3.503707   0.104384   33.57   <2e-16 ***
idade       0.096276   0.001832   52.56   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 12.75 on 60213 degrees of freedom
Multiple R-squared:  0.04387,   Adjusted R-squared:  0.04386 
F-statistic:  2763 on 1 and 60213 DF,  p-value: < 2.2e-16
Código
model1_tidy <- tidy(model1, conf.int = TRUE)
model1_tidy
# A tibble: 2 × 7
  term        estimate std.error statistic   p.value conf.low conf.high
  <chr>          <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
1 (Intercept)   3.50     0.104        33.6 9.83e-245   3.30      3.71  
2 idade         0.0963   0.00183      52.6 0           0.0927    0.0999
Código
model2 <- lm(dias_int ~ idade, data = gdh_model)

summary(model2) 

Call:
lm(formula = dias_int ~ idade, data = gdh_model)

Residuals:
   Min     1Q Median     3Q    Max 
-12.42  -5.68  -2.78   0.50 359.96 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 3.503707   0.104384   33.57   <2e-16 ***
idade       0.096276   0.001832   52.56   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 12.75 on 60213 degrees of freedom
Multiple R-squared:  0.04387,   Adjusted R-squared:  0.04386 
F-statistic:  2763 on 1 and 60213 DF,  p-value: < 2.2e-16
Código
# Regressão multivariada

model3 <- lm(dias_int ~ idade + sexo, data = gdh_model)

model3_tidy <- tidy(model3, conf.int = TRUE)
model3_tidy
# A tibble: 3 × 7
  term          estimate std.error statistic   p.value conf.low conf.high
  <chr>            <dbl>     <dbl>     <dbl>     <dbl>    <dbl>     <dbl>
1 (Intercept)     2.97     0.113        26.4 1.57e-152   2.75      3.19  
2 idade           0.0952   0.00183      52.0 0           0.0916    0.0988
3 sexoMasculino   1.29     0.104        12.4 3.30e- 35   1.09      1.50  
Código
# check_model(model3) (fica a correr e não dá resultado)

# Nenhum destes modelos se adequa ao pretendido 

6 Bibliografia/Referências

Escola Nacional de Saúde Pública (ENSP NOVA). 2025. «Fundamentos de Data Science na Saúde: Aplicações com R». Curso de formação avançada.
Nota

A versão deste documento foi atualizada no dia 17/11/2025.